#!/usr/bin/ruby
require 'cgi'

# Set this to the path of your database file.
$FILE = "database.txt"
# $FILE = "/Users/jdahern/Documents/Dropbox/CS372/ass8/database.txt"
$cgi =  CGI.new("html3")
$FRIEND_OPTIONS = {"dated" => "dated", "family" => "in my family", "friend" => "met through a friend", "work" => "worked together", "random" => "met randomly", "group" => "in a group together", "nohow" => "choose"}
###############################################################
# User
###############################################################
class User
  attr_reader :username, :name, :friends, :sex, :password
  attr_writer :username, :name, :sex, :password
  
  def initialize(username,name,friends,sex,password)
   @username = username
   @name = name
   @friends = friends
   @sex = sex
   @password = password
   # print to_out
  end

  def to_out
     s = ""
     @friends.each do |key, value|
           s = s + key + "/" + value + " "
         end
     n = "USERNAME\t#{@username}\nNAME\t\t#{@name}\nSEX\t\t#{@sex}\nPASSWORD\t#{@password}\nFRIENDS\t\t" + s + "\nEND\n\n"
     return n
  end
end
###############################################################
# Database
###############################################################
class Database
  @@db = {}
  def initialize()
    load()
  end
  
  def [] (username)
    users do |x|
      if(x[0] == username) then
        return x[1]
      end
    end
  end
  
  def []= (obj,user)
    users do |x|
      if x[1].username == user then
        x[1].name = ""
        x[1].name = obj.name
        x[1].sex = ""
        x[1].sex = obj.sex
        x[1].password = ""
        x[1].password = obj.password
        x[1].friends.clear
        obj.friends.each do |key, value|
          x[1].friends[key] = value
        end
      end
    end
    save()
  end

  def obj(username)
    person = {}
    @@db.each do |key, value|
      if(username == key) then
        return @@db[key]
      end
    end
    return person
  end 


  def friends(username)

  		f = []		
  		fr = {}	

  		@@db.each do |key, value|
  			if key == username then		
  				fr = value.friends()
  				fr.each do |key, value|
  					f << key
  				end			
  			end 
  		end
  		return f
  	end

    def allfriends(username)
      f = []
      @@db.each do |key, value|
        if(key == username) then
          # Do nothing
        else
          f << key
        end
      end
      return f
    end

  def usr()
  		data = {}
  		list = []
  		usrs = []

  		@@db.each do |key, value|
  			usrs << key 
  			list << value.friends()
  		end

  		for i in 0...list.length
  			friends = []		
  			list[i].each do |key, value|
  				friends << key			
  			end

  			data[usrs[i]] = friends
  		end
  		return data
    end

  def users()
     @@db.find do |x|
       yield x
     end 
  end
  
  def exists_user?(name)
    out = ""
    users do |x|
      out = x[0]
      if (out) == name then
        return true
      end
    end
    return false
  end
  
  def add(username, userObj)
    @@db[username] = userObj
    save()
  end
    
  
	def exists_userpswd?(username, pswd)
	out = ""
		users do |x|
			out = x[0]
			if exists_user?(username) then
				if(x[1].password == pswd) then
					return true						
				end
			else
				return false		
			end	
		end
	end

  def read_line (database)
    File.open(database) do |file|
      file.each do |line| 
        if line =~ /[\t]/ then
          yield line
        elsif line =~ /[#]/ then
            # Do Nothing
        elsif line =~ /[a-z]+|\t+/ then
          yield line
        else
            # Do Nothing
          end
      end 
    end
  end
  
  def split_line (line)
    a = []
    line.scan(/[a-z]+[ ]*[a-z]+/) do |word|
      a << word
    end
    return a
  end
  
  def load()
    username = name = password = sex = nil
    friends = {}
    data = ""
    f = File.open($FILE, "r")
    f.each_line do |line|
      friends = {}
      args = line.scan(/[^\s]+/)
        case args[0]
          when "USERNAME" then username = args[1]
          when "NAME" then output = ""; for x in 1...args.length; output += "#{args[x]} "; end; name = output
          when "SEX" then sex = args[1]
          when "PASSWORD" then password = args[1]
          when "FRIENDS" then 
            j = 1
            while j < (args.length)
              for i in args[j].scan(/[a-zA-Z0-9\/]+/)
                a = i.scan(/[a-zA-Z0-9]+/)
                friends[a[0]] = a[1]
              end
              j += 1
            end
          @@db[username] = User.new(username,name,friends,sex,password)
        end
      end
      f.close
    end
  
  def save()
    outputstr = ""
    File.open($FILE, "w") do |file|
      users do |x|
        outputstr = ""
        outputstr = x[1].to_out
        file.puts "#{outputstr}"
      end
    end
  end
end

###############################################################
#                         Facebook
###############################################################
class Facebook

# The database itself.
@@db = nil

# body is the HTML that you want to transfer to the user.
def Facebook.writeHTML(body)
   $cgi.out { 
     $cgi.html { 
       $cgi.body {
         "\n\n"+body+"\n"
       }
     }
   }
end


################################################################
# Welcome screen
################################################################

def Facebook.welcomeForm()
   return <<-END
   <center><h1>Welcome to 372book</h1></center><br>
   <form target="web.cgi">
      <center> 
         <input type="submit" name="action" value="login">
         <input type="submit" name="action" value="create account">
         <input type="hidden" name="username" value="">
      </center>
   </form>
END
end

def Facebook.welcomeScreen()
   writeHTML(welcomeForm())
end

###############################################################
#                    Login Screen
###############################################################

# This method returns two values, the first one is true
# if login was successful, false otherwise. The second 
# argument is an error message useful when the login was
# unsuccessful. (Yes, using exceptions might be better,
# but I hate exceptions.)
# Error messages returned: 
#    "cannot log in #{username}: no such user"
#    "cannot log in #{username}: wrong password"
def Facebook.login(username,password)
	
	a = Database.new	
	if(a.exists_user?(username)) then		
		if(a.exists_userpswd?(username, password)) then
			return true, ""
    else
      return false, "cannot log in #{username}: wrong password"
		end	
  else
    return false,"cannot log in #{username}: no such user" 
	end
end

# Return the HTML for the login screen. "message"
# is an error message (in case the user has made
# an unsuccessful login attempt). Leave it blank
# the first time.
def Facebook.loginForm(message)
   return <<-END
   <center>
       <h1>Log in to 372book</h1><br>
       <font color="red">#{message}</font><br>
       <form target="web.cgi">
          username: <input type="text" name="username"><br>
          password: <input type="password" name="password"><br>
          <input type="submit" name="action" value="login">
          <input type="hidden" name="username" value="">
   </form>
   </center>
END
end

def Facebook.loginScreen()
   username =  $cgi["username"]
   password =  $cgi["password"]
   if username != "" then
      Facebook.load()
      ok, message = Facebook.login(username,password)
      if ok then
         Facebook.profileScreen(username)
      else
         writeHTML(loginForm(message))
      end
   else 
      writeHTML(loginForm(""))
   end
end

################################################################
#                        Logout form
################################################################

def Facebook.logoutScreen()
   writeHTML(welcomeForm())
end

################################################################
#                 Create new user screen
################################################################

# A username consists of a letter (upper or lower case)
# followed by at least one or more letters or digits.
def Facebook.okUsername(un)
	if un =~ /[a-z]+[0-9]*/ then true else false end
end

# A password consists of four or moour letters or digits.
def Facebook.okPassword(pw)
	if pw =~ /[a-z0-9][a-z0-9][a-z0-9][a-z0-9]+/ then true else false end
end

# Sex is one of Male or Female.
def Facebook.okSex(sex)
   if sex =~ /Male|Female/ then true else false end
end

# user is an object of type User, containing all the 
# information gathered about the new user. We return
# two values, a boolean which is true if the
# user has been successfully added to the Database
# and false otherwise. On a failed attempt, one of
# these error messages is generated:
#    "cannot create #{username}: user already exists"
#    "cannot create #{username}: illegal username"
#    "cannot create #{username}: illegal password"
#    "cannot create #{username}: choose Male or Female"
def Facebook.createUser(user)
   a = Database.new
   username = user.username()
   password = user.password()
   sex = user.sex()
   if(a.exists_user?(username)) then
    return false, "cannot create #{username}: user already exists"
  elsif(!Facebook.okUsername(username)) then
      return false, "cannot create #{username}: illegal username"
    elsif(!Facebook.okPassword(password)) then
      return false, "cannot create #{username}: illegal password"
    elsif(!Facebook.okSex(sex)) then
      return false, "cannot create #{username}: choose Male or Female"
    else
      return true, ""
  end
end


# Return the HTML for the create user screen. "message"
# is an error message. Leave it blank the first time.
def Facebook.createForm(message)
   return <<-END
   <center>
       <h1>Create new account</h1><br>
       <font color="red">#{message}</font><br>
       <form target="web.cgi">
          username: <input type="text" name="username"> <br>
          name: <input type="text" name="realname"> <br>
          password: <input type="password" name="password"> <br>
          sex: <input type="radio" name="sex" value="Male"> Male
               <input type="radio" name="sex" value="Female"> Female <br>
         <input type="submit" name="action" value="save account">
         <input type="hidden" name="username" value="">
   </form>
END
end

# This is where we arrive the first time the user hits the
# create account button:
def Facebook.createScreen()
   writeHTML(createForm(""))
end

# After the user has filled in data into the account screen
# and hits "create account", we'll arrive here. We check
# for valid data and either go to the profile screen (if
# all was OK) or back to the create account screen if there
# was an error.
def Facebook.saveCreatedUser()
   username =  $cgi["username"]
   password =  $cgi["password"]
   realname =  $cgi["realname"]
   sex      =  $cgi["sex"]
   if username != "" then
       Facebook.load()
       person = User.new(username, %&\"#{realname}\"&, {}, sex, password)
       ok, message = Facebook.createUser(person)
       if ok then
         a = Database.new
         a.add(username, person)
         a.save()
        Facebook.profileScreen(username)
       else
          writeHTML(createForm(message))
       end
    else 
       writeHTML(createForm(""))
    end
end

################################################################
# User profile page
################################################################

# Do a breadth first search of the friend graph. queue holds
# a list of usernames which we have yet to process. visited
# is a hashtable from usernames to the lever where they were
# first found. The routine returns 'counts', an array where
# count[0] is the number of users at that level from the
# current user (always 0), count[1] is the user's number of
# direct friends, count[2] is the number of friends 2 steps
# away, etc.
def Facebook.BFS(queue, visited)
	level =[]
	a = Database.new	
	data = {}	

	data = a.usr()

# Since queue starts with a name, just start popping the queue
	searched = queue.pop
  level << searched
	queue.push(searched)
	while queue.length > 0
		# pop off the node
		node = queue.pop
		# get the edges of the node
		addin = data[node]
		# create the output string
		output = ""
		# scan thru the edges
		addin.each do |key, value|
			# if node has not been visited, then visit it
			if !visited[key] then
				visited[key] = true
				queue.push(key)
				output += key + " "
			end
		end
		# make sure there is output to be printed
		if output != "" then
			level <<  output
		end
	end
return level
end

# This routine returns 'counts', an array where
# count[0] is the number of users at that level from the
# current user (always 0), count[1] is the user's number of
# direct friends, count[2] is the number of friends 2 steps
# away, etc.
def Facebook.levels(username)
    visited = {username=>true}
	queue = [username]
	level =[]
	count =[]
	f = []
	friend = []
	level = BFS(queue, visited)
	for i in 0...level.length
		temp = level[i].scan(/[a-zA-Z0-9]+/)
		count << temp.length
		friend << temp
	end
	out = []
	temp = []

	for i in 0...friend.length
		temp = friend[i]
		for j in 0...friend[i].length
			out << temp[j]
		end
	end
	return count, out
end

# On the profile page we list all the user's friends,
# along with a button they can press to go to that
# person's profile page. This routine returns the
# corresponding HTML. 'friend' is the user name of
# the friend, 'how' is how we know that friend.
def Facebook.viewFriendEntry(friend,how)
   return "YOUR CODE HERE"
end

# Return the HTML for a user.
def Facebook.profileForm(username)
#  $username = username
   a = Database.new
   counts = []
   counts, nothing = Facebook.levels(username)
   # counts = [1,2,3]
   person = a[username]
   friends = person.friends()
   friendsnames = person.friends().keys
   fprofile = ""
   for i in 0...(friends.length)
          f = a[friendsnames[i]]
          fusername = f.username()
          fname = f.name()
          frel = $FRIEND_OPTIONS[friends[fusername]]
          fprofile += %&<tr><td>#{fusername}</td>
          <td>#{fname}</td>
          <td>#{frel}</td>
          <td><input type="submit" name="action" value="view #{fusername}"></td></tr>&
  end
  myfriends = ""
   plural1 = ""
   plural2 = ""
   plural3 = ""

 	if counts[1] != 1 then
 			plural1 = "s"
 	end
 	if counts[2] != 1 then
 			plural2 = "s"
 	end
 	if counts[3] != 1 then
 			plural3 = "s"
 	end

      if counts.length == 1 then
        myfriends += "<li>No immediate friend#{plural1}.</li>
              <li>No friends of friends.</li>
              <li>No friends of friends of friends.</li>"
       elsif counts.length == 2 then
 	     myfriends += "<li>#{counts[1]} immediate friend#{plural1}.</li>
            <li>No friends of friends.</li>
            <li>No friends of friends of friends.</li>"
   	 elsif counts.length == 3 then 
 		 myfriends += "<li>#{counts[1]} immediate friend#{plural1}.</li>
 		   <li>#{counts[2]} friend#{plural2} of friends.</li>
            <li>No friends of friends of friends.</li>"
 	 else
 	     myfriends += "<li>#{counts[1]} immediate friend#{plural1}.</li>
            <li>#{counts[2]} friends of friend#{plural2}.</li>
            <li>#{counts[3]} friend#{plural3} of friends of friends.</li>" 
       end
   form = <<-END
   <center><h1>Profile for #{username}</h1></center><br> 
   <form target="web.cgi">
   <ul>
       <ul>
           <li> name: #{person.name()} </li>
           <li> sex: #{person.sex()} </li>
           <li> friends:
           <table>
              #{fprofile}
           </table>
           </li>
           #{myfriends}
      </ul>
  </ul>

  <br />
   <center>
      <input type="submit" name="action" value="home"> 
      <input type="submit" name="action" value="edit"> 
      <input type="submit" name="action" value="logout">
      <input type="hidden" name="username" value="#{$username}">
  </center>
   </form>
END
end

def Facebook.profileScreen(username)
   Facebook.load()
   writeHTML(profileForm(username))
end

################################################################
#                        View friend form
################################################################


def Facebook.viewFriendScreen(friend)
   Facebook.load()
   writeHTML(profileForm(friend))
end

################################################################
#                      Edit page
################################################################

# Return the HTML for the user's edit page.
def Facebook.editForm(username)
  a = Database.new
   person = a[username]
   nothing, fr = Facebook.levels(username) # list of users friends
   f = person.friends() # hash of users friends
   if(f.length == 0) then
     fr = a.allfriends(username)
   end
   sex = ""
   # $FRIEND_OPTIONS = {"dated" => "dated", "family" => "in my family", "friend" => "met through a friend",
   #     "work" => "worked together", "ramdom" => "met randomly", "group" => "in a group together"}
  # Sex radio buttons for username
  if(person.sex() == "Male") then
     sex = %&<input type="radio" name="sex" value="Male" checked> Male <input type="radio" name="sex" value="Female"> Female&
  else
      sex = %&<input type="radio" name="sex" value="Male"> Male <input type="radio" name="sex" value="Female" checked> Female&
  end
  
  # Friends
  fout = ""
  for i in 1...fr.length
    tempusr = fr[i]
    temp = a[tempusr]
    
    # Username and Name
    fout += "<tr>
    <td>#{temp.username()} (#{temp.name()})</td>"
    
    how = ""
    group = ""
    dated = ""
    family = ""
    random = ""
    work = ""
    met = ""
    selected = "selected"
    
    if(f.has_key?(temp.username())) then
      # Friend not Foe
      fout += %&<td><input type="radio" name="#{temp.username()}-isfriend" value="yes" checked> Friend
        <input type="radio" name="#{temp.username()}-isfriend" value="no" > Not friend</td>&
      # Choices
      f.each do |key, value|
        if(key = temp.username()) then
          if(value == "dated") then
            dated = selected
          elsif(value == "family") then
            family = selected
          elsif(value == "friend") then
            random = selected
          elsif(value == "work") then
            work = selected
          elsif(value == "random") then
            met = selected
          else
            group = selected
          end
        else
          how = selected
        end
      end
    else
      # Not Friend of Foe
      fout += %&<td><input type="radio" name="#{temp.username()}-isfriend" value="yes"> Friend
        <input type="radio" name="#{temp.username()}-isfriend" value="no" checked> Not friend</td>&
      # Default
      how = selected
    end
    fout += %&<td>
            <select name="#{temp.username()}-how">
            <option value="nohow" & + how + %&>How did you meet?</option>
            <option value="group" & + group + %&>in a group together</option>
            <option value="dated" & + dated + %&>dated</option>
            <option value="family" & + family + %&>in my family</option>
            <option value="random" & + random + %&>met randomly</option>
            <option value="work" & + work + %&>worked together</option>
            <option value="friend" & + met + %&>met through a friend</option>
            </select>
            </td>
            </tr>&
  end
  
  
  
    
   form = <<-END
   <center><h1>Edit your profile information </h1></center><br>
   <form target="web.cgi">
   <ul>
         <ul>

             <li> name: <input type="text" name="name" value=#{person.name()}></li>
             <li> sex: #{sex}
             </li>
             <li> password: <input type="text" name="password" value=#{person.password()}></li>
             <li> friends:
                 <table>
 #{fout}
 </table>

             </li>
         </ul>
     </ul>
   <br>
   <center>
      <input type="submit" name="action" value="save edits">
      <input type="submit" name="action" value="home">
      <input type="submit" name="action" value="logout">
      <input type="hidden" name="username" value="#{$username}">
   </center>
END
end

# When the user first hits the EDIT-button, we land here.
def Facebook.editScreen()
   Facebook.load()
   writeHTML(editForm($username))
end

# When he hits the SAVE EDITS-button, we land here.
# Save the edits in the database, write the database
# file back out again, and return the user's profile
# page.
def Facebook.saveEdits(username)
   Facebook.load()
   a = Database.new
   person = a[username]
   
   name = $cgi['name']
   sex = $cgi['sex']
   password = $cgi['password']
   
   outname = ""
   outsex = ""
   outpassword = ""
   friends = {}
   
   cginame = name.scan(/[a-zA-Z0-9]+[ ]*[a-zA-Z0-9]+/)
   objname = person.name().scan(/[a-zA-Z0-9]+[ ]*[a-zA-Z0-9]+/)
   
   # Name Checker
   if((cginame != objname) && (name != "")) then
       outname = %&\"#{cginame}\"&
   else
     outname = person.name()
   end
  # Sex checker
   if(sex != person.sex()) then
     outsex = sex
   else
     outsex = person.sex()
   end
   # Password checker
   if(password != person.password()) then
     if(Facebook.okPassword(password)) then
       outpassword = password
     else
       outpassword = person.password()
     end
   else
     outpassword = person.password()
   end
   
   garbage, fof = Facebook.levels(username)
   
   if(friends.length == 0) then
      fof = a.allfriends(username)
      for i in 0...fof.length
        # print "\n #{fof[i]}\n"
        fr = $cgi["#{fof[i]}-isfriend"]
        choice = $cgi["#{fof[i]}-how"]
        # print "Friend: #{fr}, Choice: #{choice}\n"
        if(fr == "yes") then
          friends[fof[i]] = choice
        end
       end
    else
      for i in 1...fof.length
        # print "\n #{fof[i]}\n"
        fr = $cgi["#{fof[i]}-isfriend"]
        choice = $cgi["#{fof[i]}-how"]
        # print "Friend: #{fr}, Choice: #{choice}\n"
        if(fr == "yes") then
          friends[fof[i]] = choice
        end
      end
    end
   
   # Create new object
    outobj = User.new(username, outname, friends, outsex, outpassword)
   
   # Rehash
    a.[]=(outobj,username)

   a.save()

   Facebook.save()
   Facebook.profileScreen(username)
end

################################################################
#                  Database load and store
################################################################

def Facebook.load()
  @@db = Database.new
  @@db.load()
  return true, "database loaded"
end

def Facebook.save()
  @@db.save()
  return true, "database saved"
end

end


################################################################
# Main section
################################################################


action = $cgi["action"]
$username = $cgi['username']
#STDERR.puts "ACTION="+action + " username=" + $username
case action
   when ""               then Facebook.welcomeScreen()
   when "login"          then Facebook.loginScreen()
   when "logout"         then Facebook.logoutScreen()
   when "create account" then Facebook.createScreen()
   when "save account"   then Facebook.saveCreatedUser()
   when "edit"           then Facebook.editScreen()
   when "save edits"     then Facebook.saveEdits($username)
   when "home"           then Facebook.profileScreen($username)
   when /^view [a-zA-Z0-9]+$/         then Facebook.viewFriendScreen($cgi["action"].scan(/[a-zA-Z0-9]+$/)[0])
end
